from collections import namedtuple

import mongoengine

from common.config import MONGODB_HOST, MONGODB_DB, OUTER_URL

mongoengine.connect(MONGODB_DB, host=MONGODB_HOST)


class Base:
    name_index = []

    @classmethod
    def get_name_index(cls):
        return cls.name_index


class Ask(mongoengine.Document, Base):
    date = mongoengine.IntField()
    url = mongoengine.StringField()
    method = mongoengine.StringField()
    headers = mongoengine.DictField()
    data = mongoengine.DictField()
    form = mongoengine.DictField()
    resp_header = mongoengine.ListField(mongoengine.ListField(mongoengine.StringField()))
    resp_content = mongoengine.BinaryField()
    resp_stat = mongoengine.IntField()
    hash_code = mongoengine.StringField(primary_key=True)
    source_type = mongoengine.StringField()
    has_file = mongoengine.BooleanField()
    is_Available = mongoengine.BooleanField()
    analysed = mongoengine.BooleanField()
    name = '请求'
    name_index = ['url', 'method', 'headers', 'data', 'form', 'resp_header', 'resp_content', 'resp_stat', 'hash_code',
                  'source_type', 'is_Available']

    def make_key(self):
        import hashlib
        md = hashlib.md5()
        if self.url:
            md.update(self.url.encode())
        if self.method:
            md.update(self.method.encode())
        if AskProxySetting.md_use_data():
            md.update(str(self.data).encode())
        if AskProxySetting.md_use_header():
            md.update(str(self.headers).encode())
        if AskProxySetting.md_use_result():
            md.update(str(self.resp_content).encode())
        return md.hexdigest()

    def __str__(self):
        return "{} \n {}".format(self.url, self.hash_code)

    def send_again(self, headers=None, data=None):
        if headers is None:
            headers = self.headers
        if data is None:
            data = self.data
        import requests
        return requests.request(self.method,
                                "{}/{}".format(OUTER_URL, '/'.join(self.url.split('/')[3:])),
                                headers=headers,
                                data=data,
                                allow_redirects=False)


class CheckResult(mongoengine.Document, Base):
    name = '检查结果'
    结果 = mongoengine.StringField()
    原始请求 = mongoengine.ReferenceField(Ask)
    失败原因 = mongoengine.StringField()
    返回值 = mongoengine.StringField()
    name_index = ['结果', '原始请求', '失败原因', '返回值']


class ParamGroupType(mongoengine.Document, Base):
    uid = mongoengine.IntField(primary_key=True)
    # 参数的组类型
    group_type = mongoengine.StringField()  # CLASS GROUP_TYPE 选一 和绑定出现

    def clean(self):
        if not self.uid:
            md = ParamGroupType.objects().order_by("-uid").first()
            if md:
                self.uid = md.uid + 1
            else:
                self.uid = 1


class RawValue(mongoengine.Document, Base):
    uid = mongoengine.IntField(primary_key=True)
    list_value = mongoengine.ListField(mongoengine.IntField())
    dict_value = mongoengine.DictField()
    val_type = mongoengine.StringField()
    # 下面是一些元信息 ，需要随着理解逐步增加
    required = mongoengine.BooleanField()  # 该信息是否是必须出现的
    value_range_type = mongoengine.StringField()
    # 用于int,float,str,bool 基本类型，用于界定范围，有enum(枚举),flow(流),unbound(无限制),range(数字区域)
    value_range = mongoengine.StringField()
    available = mongoengine.BooleanField()
    # 组类型，如果组类型有，那么在参数组合的时候需要考虑这个问题
    groups = mongoengine.ListField(mongoengine.ReferenceField(ParamGroupType))

    def get_grouped_data(self, data):
        """
        获取参数中根据组类型构建的参数（只有组的参数）
        :param data: 一次成功的请求示例
        :return:
        """
        routes = self.get_sub_route()
        for route in routes:
            arg = self.get_sub_arg(route)
            if arg.groups:  # 包含组
                if not (route.get_data_by_routes(data) is None):
                    yield route

    def get_require_route(self, required=False):
        """
        获取不是必要参数的路径
        :return:
        """
        routes = self.get_sub_route()
        for route in routes:
            if self.get_sub_arg(route).required == required:
                yield route

    def get_sub_arg(self, key):  # 通过路由获取子参数
        if not key:
            return self
        res = None
        if self.dict_value:
            res = RawValue.get_by_uid(self.dict_value[key[0]]).get_sub_arg(key[1:])
        elif self.list_value:
            res = RawValue.get_by_uid(self.list_value[key[0]]).get_sub_arg(key[1:])
        return res

    def get_sub_route(self):  # 获取子参数的路由
        from .tools import get_data_route
        return get_data_route(self.get_data())

    def get_data(self):  # 获取所有子结构
        res = self
        if self.dict_value:  # 字典结构
            res = {}
            for key in self.dict_value:
                res[key] = RawValue.get_by_uid(self.dict_value[key]).get_data()
        elif self.list_value:
            res = []
            for item in self.list_value:
                res.append(RawValue.get_by_uid(item).get_data())
        return res

    @staticmethod
    def get_last_uid():
        r = RawValue.objects().order_by('-uid').first()
        if r:
            return r.uid + 1
        return 1
        # 枚举则用json-list，流则保存 url-result-route 流来源，range 则保存json-list(tuple)的区间数组

    @staticmethod
    def get_by_uid(uid):
        return RawValue.objects(uid=uid).first()

    def get_json(self, pid=0, id=1):
        from common.constant import BaseTypeList
        table_name = namedtuple('table_name',
                                ('id', 'pid', 'name', 'type', 'required', 'range_type', 'range_value', 'group_type',
                                 'group'))
        table_name = table_name('id', 'pid', 'name', 'type', 'required', 'range_type', 'range_value', 'group_type',
                                'group')
        res = [{table_name.id: id,
                table_name.pid: pid,
                table_name.group: [gb.uid for gb in self.groups],
                table_name.group_type: [gb.group_type for gb in self.groups],
                table_name.range_type: self.value_range_type,
                table_name.type: self.val_type,
                table_name.required: self.required,
                table_name.name: "", }]
        if self.val_type in map(lambda x: str(x()), BaseTypeList):
            res[0][table_name.range_value] = self.value_range
            res[0][table_name.type] = self.val_type
        elif self.val_type == 'list':
            for i, item in enumerate(map(RawValue.get_by_uid, self.list_value)):
                nid = id + len(res)
                for t in item.get_json(id, nid):
                    if not t['name']:
                        t['name'] = str(i)
                    res.append(t)
        else:
            for key in self.dict_value:
                item = RawValue.get_by_uid(self.dict_value[key])
                nid = id + len(res)
                for t in item.get_json(id, nid):
                    if not t['name']:
                        t['name'] = key
                    res.append(t)
        return res


class APIModel(mongoengine.Document, Base):
    url = mongoengine.StringField(primary_key=True)
    args = mongoengine.ReferenceField(RawValue)
    result = mongoengine.ReferenceField(RawValue)
    endpoint = mongoengine.StringField()
    need_login = mongoengine.BooleanField()
    user_group = mongoengine.ListField(mongoengine.StringField())
    comment = mongoengine.StringField()  # 注释
    has_file = mongoengine.BooleanField()

    aboutSql = mongoengine.DictField()
    # table->type->[raw_comment]

    API_TYPE = mongoengine.StringField()  # 查询类，修改类，新增类，

    def get_relate_url(self):
        return '/'.join(self.url.split('/')[3:])

    def get_asks_count(self):
        return Ask.objects(url=self.url).count()

    def only_selected(self):
        for table in self.aboutSql:
            for sql_type in self.aboutSql[table]:
                if sql_type not in ['SELECT', 'UNKNOWN']:
                    return False
        return True

    def is_ignored(self):
        if IgnoreApi.objects(url=self.url).first():
            return True
        return False

    def get_questions(self):
        yield from Question.objects(about=self.url)


class MiddleState(mongoengine.Document, Base):
    isBuildModel = mongoengine.BooleanField()  # 是否正在构建模型


class AskProxySetting(mongoengine.Document, Base):
    listenAsk = mongoengine.BooleanField()  # 是否监听
    use_data = mongoengine.BooleanField()
    use_result = mongoengine.BooleanField()
    use_header = mongoengine.BooleanField()

    @classmethod
    def get_state(cls):
        state = AskProxySetting.objects().first()
        if state is None:
            state = AskProxySetting()
            state.listenAsk = True
            state.use_data = True
            state.use_result = True
            state.save()
        return state

    @classmethod
    def md_use_data(cls):
        state = cls.get_state()
        return state.use_data

    @classmethod
    def md_use_header(cls):
        state = cls.get_state()
        return state.use_header

    @classmethod
    def md_use_result(cls):
        state = cls.get_state()
        return state.use_result

    @classmethod
    def is_on_listen(cls):
        state = cls.get_state()
        return state.listenAsk


class SqlAsk(mongoengine.Document, Base):
    rawcomment = mongoengine.StringField()
    time = mongoengine.IntField()

    meta = {
        "collection": 'SQLAsk'
    }


class SqlModel(mongoengine.Document, Base):  # 数据库模型
    table_name = mongoengine.StringField()
    fields = mongoengine.ListField(mongoengine.ListField(mongoengine.StringField()))


class QuestionState(mongoengine.Document, Base):
    que_type = mongoengine.StringField(primary_key=True)
    auto_solve_available = mongoengine.BooleanField()  # 是否支持自动解决

    def get_type_desc(self):
        from .constant import QuestionComment
        return QuestionComment[self.que_type]

    def can_auto_solve(self):
        from solvers import solvers
        if self.que_type in solvers:
            return True
        return False


class Question(mongoengine.Document, Base):  # 问题：在 系统运行中 提出的一些启发式问题，如果可以解决将为系统理解API带来帮助
    key = mongoengine.StringField(primary_key=True)
    que_type = mongoengine.StringField()  # 问题类型，
    que_content = mongoengine.StringField()
    about = mongoengine.StringField()  # 问题相关的URL
    result = mongoengine.StringField()  # 问题的答案
    solved = mongoengine.BooleanField()

    def can_auto_solve(self):
        from solvers import solvers
        if self.que_type in solvers:
            return True
        return False

    def get_type_desc(self):
        from .constant import QuestionComment
        return QuestionComment[self.que_type]

    def clean(self):
        self.update_model()

    def update_model(self):
        if not self.solved:
            return
        model = APIModel.objects(url=self.about).first()
        if not model:
            return
        from .constant import QuestionType
        import json
        import markdown
        if self.que_type == QuestionType.IS_LOGIN:
            model.need_login = json.loads(self.result)
        elif self.que_type == QuestionType.ABOUT_SQL:
            model.aboutSql = json.loads(self.result)
        elif self.que_type == QuestionType.API_APPLICATION:
            model.comment = markdown.markdown(self.result)
        elif self.que_type == QuestionType.HAS_GROUP:
            model.user_group = json.loads(self.result)
        elif self.que_type == QuestionType.PARAM_TYPE:
            for item in json.loads(self.result):
                fresh_goal = model.args.get_sub_arg(item[0])
                fresh_goal.val_type = item[1]
                fresh_goal.save()
        elif self.que_type == QuestionType.PARAM_IS_NECESSARY:
            for item in json.loads(self.result):
                fresh_goal = model.args.get_sub_arg(item)
                fresh_goal.required = False
                fresh_goal.save()
        elif self.que_type == QuestionType.PARAM_RANGE:
            for item in json.loads(self.result):
                fresh_goal = model.args.get_sub_arg(item[0])
                fresh_goal.value_range = item[1]
                fresh_goal.save()
        elif self.que_type == QuestionType.PARAM_HAS_GROUP:
            for item in json.loads(self.result):
                fresh_goal = model.args.get_sub_arg(item[0])
                fresh_goal.groups = item[1]
                fresh_goal.save()
        model.save()

    meta = {
        'indexes': [
            'about',
        ]
    }


class State(mongoengine.Document, Base):
    is_building = mongoengine.BooleanField()  # 是否在构建模型中


class IgnoreApi(mongoengine.Document, Base):
    url = mongoengine.StringField(primary_key=True)  # 被忽略的API
